package cz.cvut.manatee.processManager;

/*
 * Trida vlakna (thread) bude vykonavat paralelne prichozi dotazy
 */
public class ThreadRunnable implements Runnable {
	// ******************** STATICKE PROMENNE ********************
	/*
	 * pocet vytvorenych vlaken (instanci)
	 */
	private static volatile int threadNumber = 0;
	
	
	
	
	// ******************** INSTANCNI PROMENNE ********************
	
	/*
	 * Informace zda vlakno ma dotaz k vykonavani (nebo jej vykonava)
	 */
	protected volatile boolean busy = true;
	
	/*
	 * Doba pred jakou vlakno naposledy vykonavalo nejaky ukol
	 * Pocitano v iteracich
	 */
	protected volatile int age = 0;
	
	/*
	 * Prave vykonavany dotaz
	 */
	protected volatile Query query = null;

	/*
	 * Reference na pridelovac dotazu
	 */
	protected Assigner assigner;
	

	/*
	 * instance spusteneho vlakna (sebe)
	 */
	protected Thread thread = null;
	
	
	
	
	
	
	// ******************** STATICKE METODY ********************
	protected static synchronized void threadNumberIncrement() {
		ThreadRunnable.threadNumber++;
	}
	
	protected static synchronized void threadNumberDecrement() {
		ThreadRunnable.threadNumber--;
	}
	

	
	
	// ******************** INSTANCNI METODY ********************
	public ThreadRunnable(Assigner assigner) {
		ThreadRunnable.threadNumberIncrement(); // zvysi pocet existujicich vlaken
		this.assigner = assigner;
	}
	
	
	public synchronized void setAssigner(Assigner assigner) {
		if (!this.isBusy()) {
			//pokud vlakno nepracuje, musime novemu assigneru oznamit, ze ma nove vlakno - zvednutim semaforu
			// pokud pracuje, zvedne mu ho po dokonceni sam (bude mit noveho assignera)
			assigner.semThreadRelease();
		}
		this.assigner = assigner;
	}
	
	
	/*
	 * Vraci stari vlakna (pred kolika iteracemi naposledy pracovalo)
	 */
	public synchronized int getAge() {
		return this.age;
	}
	
	/*
	 * Nastavi stari vlakna na 0
	 */
	public synchronized void resetAge() {
		this.age = 0;
	}
	
	/*
	 * Zvysi stari vlakna o jednu iteraci
	 */
	public synchronized void incrementAge() {
		if (this.age < Integer.MAX_VALUE) {
			this.age++;
		}
	}
	
	/*
	 * Pracuje vlákno?
	 * Neblokujici volani, aby nedochazelo v deadlocku v process manageru (necekali vzajemne na sebe)
	 */
	public boolean isBusy() {
		return this.busy;
	}
	
	public boolean isAlive() {
		return this.thread.isAlive();
	}
	
	
	public boolean isAliveAndNotBusy() {
		return (this.thread.isAlive()) && (!this.busy);
	}
	
	
	protected synchronized void setNotBusy() {
		this.busy = false;
	}
	
	protected synchronized void setBusy() {
		this.busy = true;
	}
	
	/*
	 * Ziskani prave vykonavaneho dotazu
	 */
	public synchronized Query getQuery() {
		return this.query;
	}
	
	/*
	 * Nastavi dotaz, ktery ma vlakno provadet a vlakno probudi
	 */
	public synchronized void setQuery(Query dotaz) throws AddingNotWaitingQueryException, AddingEmptyQueryException {
		if (dotaz == null) throw new AddingEmptyQueryException();
		if (!dotaz.isStateWaiting()) throw new AddingNotWaitingQueryException();
		
		this.setBusy();
		this.query = dotaz;
		try {
			this.query.setExecuting();
		} catch (ProcessManagerException e) {
			System.out.println(e);
		}
		
		this.notify(); // vzbudi sam sebe
	}
	
	
	public void setThread(Thread thread) {
		this.thread = thread;
	}
	
	

	/*
	 * Vlastni vykonna cast vlakna
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		try {
			while (true) {
					//atomicke, zamek na vlakne
					synchronized (this) {
						this.setNotBusy();
						this.assigner.semThreadRelease();
						this.wait();
					}
				
				if (this.isBusy()) {
					// je pripraven vykonavat dotaz
					this.resetAge();
					Query dotaz = this.getQuery();
					try {
						try {
							dotaz.execute(this);
						} catch (RuntimeException re) {
							// chyba v provadeno dotazu
							dotaz.setErrorResult("Manatee executing error: " + re.toString());
							Logging.get().info("Chyba v provadeni dotazu: " + dotaz + ". Chyba: " + re.toString());
						}
						
						// atomicke, zamek na Query
						synchronized (dotaz) {
							dotaz.setSolved();
							dotaz.notify();
						}
					} catch (ProcessManagerException e) {
						System.out.println(e);
					}
				}
				// proces je oznacen jako NotBusy na zacatku cyklu
			}
		} catch (InterruptedException ie) {
			this.setBusy();
		}

		Logging.get().info("vlakno: ukoncuji se "+this);
		
		ThreadRunnable.threadNumberDecrement();
	}
}